package ms.jasim;

import jason.asSyntax.Literal;
import jason.bb.BeliefBase;
import jason.infra.centralised.CentralisedAgArch;
import jason.infra.centralised.RunCentralisedMAS;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import ms.jacrim.framework.ActiveSolution;
import ms.jacrim.framework.Event;
import ms.jacrim.pddl.PddlSolution;
import ms.utils.SocketUtil;

public class JasimServer {

	private static final long serialVersionUID = -7500948837973795512L;

	private boolean shutdown;
	public final JasimEnv environment;
	private ServerSocket server;
	private List<JasimServerClient> clients;

	public class JasimServerClient implements Runnable {
		private SocketUtil socket;

		private boolean clientShutdown = false;

		public JasimServerClient(Socket socket) throws IOException {
			this.socket = new SocketUtil(socket);
			synchronized (clients) {
				clients.add(this);
			}
		}

		@Override
		public void run() {
			String cmd;
			while (!shutdown && !clientShutdown) {

				try {
					if (!socket.isAvailable())
						clientShutdown = true;
					else {
						cmd = socket.readString();
						if (cmd != null) {
							// System.out.println("Receive: " + cmd);
							String[] args = cmd.split(" ", 2);
							if (args.length > 0) {
								args[0] = args[0].toLowerCase();
								if ("add".equals(args[0]))
									processAddCommand(args[0], args[1]);
								else if ("remove".equalsIgnoreCase(args[0]))
									processRemoveCommand(args[0], args[1]);
								else if ("fulfill".equalsIgnoreCase(args[0]))
									processFulfillCommand(args[1]);
								else if ("view".equals(args[0]))
									processViewCommand(args.length > 1 ? args[1] : null);
								else if ("close".equals(args[0]))
									clientShutdown = true;
								else if ("fail".equalsIgnoreCase(args[0]))
									processFailCommand(args[1]);
								else if ("query".equalsIgnoreCase(args[0]))
									processQueryCommand(args[1]);
							}
						}
					}
				} catch (IOException e) {
					clientShutdown = true;
				}
			}
			shutdown();
		}

		private void processQueryCommand(String arg) throws IOException {
			String[] p = arg.split(" ");

			send("DATA");
			if ("active_solution".equalsIgnoreCase(p[0])) {
				environment.logger.info("Query active solution");
				socket.writeObject(environment.Model.SolutionManager.ActiveSolution);
				environment.logger.info(">>active solution returned");
			}
			else if ("events".equalsIgnoreCase(p[0])) {
				socket.writeObject((ArrayList<Event>) environment.Model.Events);
			}
		}

		private void processFailCommand(String action) {
			Literal r = Literal.parseLiteral(action);
			if (r != null) {
				System.out.println("fail: " + r.toString());
				String agName = r.getTerm(0).toString();
				addPercept(agName, "fail_" + r.toString());
			}
			else
				System.out.println("Unable to parse literal: " + action);
		}

		private void processFulfillCommand(String arg) {
			ActiveSolution plan = environment.Model.SolutionManager.ActiveSolution;
			for (int i = 0; i < plan.ActionCount(); i++) {
				PddlSolution.Action a = plan.getAction(i);
				if ("SATISFIES".equalsIgnoreCase(a.Name()) && a.Argument(1).equalsIgnoreCase(arg)) {
					addPercept(a.Argument(0).toLowerCase(), String.format("fulfilled(%s)", a.Argument(1).toLowerCase()));
				}
			}
		}

		private void processViewCommand(String arg) throws IOException {
			synchronized (socket) {
				send("DATA");
				environment.logger.info("view command: " + arg);
				String[] p = arg != null ? arg.split(" ", 2) : null;
				if (p == null || "status".equalsIgnoreCase(p[0]))
					viewAgentStatus(p != null && p.length > 1 ? p[1] : null);
				else if ("plan".equalsIgnoreCase(p[0]))
					viewActivePlan(p.length > 1 ? p[1] : null);
				else if ("mind".equalsIgnoreCase(p[0]))
					viewAgentMind(p[1]);
				environment.logger.info("view command: done");
				send("DONE");
			}
		}

		private void viewAgentMind(String arg) throws IOException {
			String agName = arg;
			CentralisedAgArch arch = RunCentralisedMAS.getRunner().getAg(agName);

			if (arch != null) {
				send("AGENT %s", agName);
				BeliefBase bb = arch.getUserAgArch().getTS().getAg().getBB();
				for (Literal b : bb) {
					send(b.toString());
				}
			} else
				send("ERROR agent not found.");
		}

		private void viewActivePlan(String arg) throws IOException {
			ActiveSolution plan = environment.Model.SolutionManager.ActiveSolution;
			boolean viewall = "all".equalsIgnoreCase(arg);

			for (int i = 0; i < plan.ActionCount(); i++) {
				PddlSolution.Action a = plan.getAction(i);
				ActiveSolution.ActionInfo info = plan.findActionInfo(a);
				if (info != null)
					send("%s [%s]", a.toString(), info.toString());
				else if (viewall)
					send(a.toString());
			}
		}

		private void viewAgentStatus(String string) throws IOException {
			Set<String> agents = environment.RT.getAgentsNames();
			String pendingTasks, passingTasks, passedTasks;
			String fulfillTask;

			for (String agName : agents)
				if (!JasimEnv.JASIM_AGENT.equalsIgnoreCase(agName)) {
					send("AGENT %s", agName);

					pendingTasks = passingTasks = passedTasks = fulfillTask = "";
					CentralisedAgArch arch = RunCentralisedMAS.getRunner().getAg(agName);

					BeliefBase bb = arch.getUserAgArch().getTS().getAg().getBB();
					if (bb != null)
						for (Literal p : bb) {
							String functor = p.getFunctor();
							if (functor.equalsIgnoreCase("fulfilling"))
								fulfillTask += (fulfillTask.length() > 0 ? ", " : "") + p.getTerm(0).toString();
							else if (functor.equalsIgnoreCase("pending_task")) {
								Literal l = (Literal) p.getTerm(0);
								if (l.getFunctor().equals("fulfill"))
									pendingTasks += (pendingTasks.length() > 0 ? ", " : "") + p.getTerm(1).toString();
							} else if (functor.equalsIgnoreCase("delegated"))
								passedTasks += (passedTasks.length() > 0 ? ", " : "")
										+ String.format("<%s, %s>", p.getTerm(1).toString(), p.getTerm(2).toString());
							else if (functor.equalsIgnoreCase("delegating"))
								passingTasks += (passingTasks.length() > 0 ? ", " : "")
										+ String.format("<%s, %s>", p.getTerm(1).toString(), p.getTerm(2).toString());
						}
					if (fulfillTask.length() > 0)
						send("SATISFYING %s", fulfillTask);
					if (pendingTasks.length() > 0)
						send("PENDING %s", pendingTasks);
					if (passingTasks.length() > 0)
						send("PASSING %s", passingTasks);
					if (passedTasks.length() > 0)
						send("PASSED %s", passedTasks);
				}
		}

		private void processAddCommand(String cmd, String arg) {
			String[] cmds = arg.split(" ", 2);
			if ("percept".equalsIgnoreCase(cmds[0])) {
				String[] p = cmds[1].split(" ", 2);
				addPercept(p[0], p[1]);
			}
		}

		private void processRemoveCommand(String cmd, String arg) {
			String[] cmds = arg.split(" ", 2);
			if ("percept".equalsIgnoreCase(cmds[0])) {
				String[] p = cmds[1].split(" ", 2);
				removePercept(p[0], p[1]);
			}
		}

		public void send(String cmd, Object... args) throws IOException {
			synchronized (socket) {
				socket.writeString(cmd, args);
			}
		}

		public void shutdown() {
			clientShutdown = true;

			synchronized (clients) {
				clients.remove(this);
			}

			try {
				send("shutdown");
				socket.close();
			} catch (IOException e) {
			}
		}
	
		public void notifyClient(String info) throws IOException {
			send("notify %s", info);
		}
	}

	public JasimServer(JasimEnv env) {
		environment = env;
		clients = new ArrayList<JasimServerClient>();
	}

	public void run() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					int port = 6668;
					server = new ServerSocket(port);
					System.out.println("JasimServer starts up, listenting port: " + port);

					while (!shutdown) {
						JasimServerClient w = null;
						w = new JasimServerClient(server.accept());
						new Thread(w).start();
					}
					server.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	public void shutdown() {
		shutdown = true;

		while (clients.size() > 0)
			clients.get(clients.size() - 1).shutdown();

	}

	public void addPercept(String agentName, String percept) {
		environment.addPercept(agentName, Literal.parseLiteral(percept));
	}

	public void removePercept(String agName, String percept) {
		environment.removePercept(agName, Literal.parseLiteral(percept));
	}

	public void raiseEvent(JasimEventArgument jasimEventArgument) {
		// notifyClient("");
	}

	public void notifyClient(String info) {
		for (JasimServerClient client : clients)
			try {
				client.notifyClient(info);
			} catch (IOException e) {
			}
	}
	
	public static void main(String[] args) {
		try {
			System.setProperty("java.security.policy", "java.policy");
			JasimServer server = new JasimServer(null);
			server.run();
			BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
			stdIn.readLine();
			server.shutdown();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}
